#include "UDPNet.h"

#include <sstream>
#include "misc/Debug.h"
#include "misc/Event.h"
/*********************************************************/
void UDPNet::run()
{
	Debug& DEBUG = Debug::Instance();
	Event* event = Event::Instance(eventStr);
	int				received = 0;
	char				*buffer = new char[UDP_PACKET_SIZE_LIMIT];
	std::string			message;

	DEBUG << "UDPNet :: Starting listening thread" << std::endl;
	mutex.lock();
	while ( !halt ) {	
		mutex.unlock();
		Message msg;

		// Read bytes from the socket if available
		received = sock.receiveFrom(buffer, UDP_PACKET_SIZE_LIMIT, msg.addr);
		message = std::string(buffer,received);
		DEBUG << "UDPNet :: Received message: " << message << std::endl;
		msg.msg = message;

		mutex.lock();
		data.push(msg);
		event->set();
	}
	mutex.unlock();
	DEBUG << "UDPNet :: Listening thread exiting" << std::endl;
	delete[](buffer);
}
/*********************************************************/
void UDPNet::start(const std::string& eventStr)
{
	UDPNet::eventStr = eventStr;
	Event* event = Event::Instance(eventStr);

	// Do nothing if the thread is allready running
	if ( lThread.isRunning() ) return;

	mutex.lock();
	halt = false;
	while ( !data.empty() ) data.pop();
	event->reset();
	mutex.unlock();

	lThread.start(*this);
	usleep(100);		// Give the thread some time to start
}
/*********************************************************/
void UDPNet::stop()
{
	// Do nothing if the thread isn't running
	if ( !lThread.isRunning() ) return;

	mutex.lock();
	// Set the halt flag
	halt = true;

	// Mimick an udp message to flag a quit
	Poco::Net::DatagramSocket	socket;
	socket.connect(localAddr);

	// Send the message QUIT to the local socket
	socket.sendBytes("QUIT",4);
	mutex.unlock();
	lThread.join();
	usleep(100);
}
/*********************************************************/
bool UDPNet::available() const
{
	mutex.lock();
	if ( data.empty() ) {
		mutex.unlock();
		return( false );
	} else {
		mutex.unlock();
		return( true );
	}
}
/*********************************************************/
std::string UDPNet::pop(Poco::Net::SocketAddress& addr)
{
	mutex.lock();
	std::string 	hoq = data.front().msg;
	addr = data.front().addr;
	data.pop();
	mutex.unlock();
	return( hoq );
}
/*********************************************************/
std::string UDPNet::pop()
{
	mutex.lock();
	std::string	hoq = data.front().msg;
	data.pop();
	mutex.unlock();
	return( hoq );
}
/*********************************************************/
void UDPNet::send(const std::string& msg)
{
	if ( msg.length() > UDP_PACKET_SIZE_LIMIT ) {
		throw(std::logic_error("Data is to large to fit into a datagram packet!"));
	}

	mutex.lock();
	Debug& DEBUG = Debug::Instance();
	if ( sock.sendTo(msg.data(), msg.length(), targetAddr) < ((signed int)msg.length()) ) {
		DEBUG << "UDPNet :: Couldn't send msg" << msg << std::endl;
	} else {
		DEBUG << "UDPNet :: Sent: '" << msg << "' To: " << targetAddr.toString() << std::endl;
	}
	mutex.unlock();
}
/*********************************************************/
void UDPNet::sendTo(const std::string& msg, const std::string& host)
{
	if ( msg.length() > UDP_PACKET_SIZE_LIMIT ) {
		throw(std::logic_error("Data is to large to fit into a datagram packet!"));
	}

	mutex.lock();
	Debug& DEBUG = Debug::Instance();
	if ( sock.sendTo(msg.data(), msg.length(), Poco::Net::SocketAddress(host)) < ((signed int)msg.length()) ) {
		DEBUG << "UDPNet :: Couldn't send msg" << msg << std::endl;
	} else {
		DEBUG << "UDPNet :: Sent: '" << msg << "' To: " << targetAddr.toString() << std::endl;
	}
	mutex.unlock();
}
/*********************************************************/
void UDPNet::getLocalAddress(std::string& host, int& port)
{
	mutex.lock();
	host = localAddr.host().toString();
	port = localAddr.port();
	mutex.unlock();
}
/*********************************************************/
void UDPNet::getResponseAddress(std::string& host, int& port)
{
	mutex.lock();
	host = targetAddr.host().toString();
	port = localAddr.port();
	mutex.unlock();
}
/*********************************************************/
void UDPNet::setLocalAddress(const std::string& host, const int port)
{
	mutex.lock();
	Poco::Net::SocketAddress	newAddr(host,port);
	localAddr.swap(newAddr);
	sock.bind(localAddr);
	mutex.unlock();
}
/*********************************************************/
void UDPNet::setResponseAddress(const std::string& host, const int port)
{
	mutex.lock();
	Poco::Net::SocketAddress	newAddr(host,port);
	targetAddr.swap(newAddr);
	mutex.unlock();
}
/*********************************************************/
unsigned int UDPNet::getAvailablePort(const std::string& host, const unsigned int min, const unsigned int max)
{
	if ( min > max ) {
		throw(std::logic_error("Cannot pick a port from an invalid port range!"));
	}

	for ( unsigned int i = min; i<=max; i++ ) {
		Poco::Net::DatagramSocket       testSocket;                     // UDP Test Socket
                Poco::Net::SocketAddress        addr(host,i);
                
                try {
                        testSocket.bind(addr);
                } catch(Poco::IOException& e) {
                        continue;               // We caught an exception, indicating theres something wrong with the socket... retry another port 
                }

                testSocket.close();
                return( i );
	}

	throw std::logic_error("Could not find an available port within the given port range!");
}
/*********************************************************/
UDPNet::UDPNet(const std::string& host, const int port)
{
	mutex.lock();
	// set the local address
	Poco::Net::SocketAddress	newAddr(host,port);
	sock.setReuseAddress(true);
	localAddr.swap(newAddr);
	sock.bind(localAddr);
	mutex.unlock();
}
/*********************************************************/
UDPNet::~UDPNet()
{
}
